package com.epoint.arcsoftface.presenter;

import android.graphics.Bitmap;
import android.graphics.Rect;
import android.hardware.Camera;
import android.renderscript.Allocation;
import android.renderscript.Element;
import android.renderscript.RenderScript;
import android.renderscript.ScriptIntrinsicYuvToRGB;
import android.renderscript.Type;

import android.util.Log;
import android.view.SurfaceView;

import com.arcsoft.face.AgeInfo;
import com.arcsoft.face.ErrorInfo;
import com.arcsoft.face.FaceEngine;
import com.arcsoft.face.FaceFeature;
import com.arcsoft.face.FaceInfo;
import com.arcsoft.face.GenderInfo;
import com.arcsoft.face.LivenessInfo;
import com.arcsoft.face.enums.DetectFaceOrientPriority;
import com.arcsoft.face.enums.DetectMode;

import com.epoint.arcsoftface.R;
import com.epoint.arcsoftface.arcsoft.ConfigUtil;
import com.epoint.arcsoftface.arcsoft.DrawHelper;
import com.epoint.arcsoftface.arcsoft.face.FaceHelper;
import com.epoint.arcsoftface.arcsoft.face.FaceListener;
import com.epoint.arcsoftface.arcsoft.face.LivenessType;
import com.epoint.arcsoftface.arcsoft.face.RecognizeColor;
import com.epoint.arcsoftface.arcsoft.face.RequestFeatureStatus;
import com.epoint.arcsoftface.arcsoft.face.RequestLivenessStatus;
import com.epoint.arcsoftface.arcsoft.model.DrawInfo;
import com.epoint.arcsoftface.arcsoft.model.FacePreviewInfo;
import com.epoint.arcsoftface.arcsoft.widget.FaceRectView;
import com.epoint.arcsoftface.view.FaceCompareView;
import com.epoint.common.base.ComomAction;
import com.epoint.common.camera.CameraListener;
import com.epoint.common.utils.BitmapUtils;
import com.epoint.common.utils.FileConfig;
import com.epoint.core.application.AppUtil;
import com.epoint.core.util.task.FrmAsynTask;
import com.epoint.ui.baseactivity.control.IPageControl;
import com.epoint.ui.widget.toast.ToastUtil;
import com.google.gson.JsonObject;

import java.io.File;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

import androidx.annotation.Nullable;
import io.reactivex.Observable;
import io.reactivex.Observer;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;

/**
 * 作者： zhangj
 * 创建时间： 2020/2/6 10:23
 * 版权： 江苏国泰新点软件有限公司
 * 版本： [1.0 2020/2/6 ]
 * 描述： 多因子检测
 */
public class FaceLiveDetectPresenter implements FaceListener, CameraListener {

    IPageControl pageControl;

    private boolean isInit = false;  //人证初始化成功

    Map<String, String> dataMap = new HashMap<>();
    String signBase64 = "";

    /**
     * 绘制人脸框的控件
     */
    private FaceRectView faceRectView;


    /**
     * VIDEO模式人脸检测引擎，用于预览帧人脸追踪
     */
    private FaceEngine ftEngine;
    /**
     * 用于特征提取的引擎
     */
    private FaceEngine frEngine;
    /**
     * IMAGE模式活体检测引擎，用于预览帧人脸活体检测
     */
    private FaceEngine flEngine;

    private static final int MAX_DETECT_NUM = 10;

    private int ftInitCode = -1;
    private int frInitCode = -1;
    private int flInitCode = -1;

    private List<FaceFeature> faceFeatures = new ArrayList();


    private FrmAsynTask task;


    private DrawHelper drawHelper;
    private FaceHelper faceHelper;

    private SurfaceView cameraView;

    private Camera.Size previewSize;

    FaceCompareView baseView;

    /**
     * 活体检测的开关
     */
    private boolean livenessDetect = true;
    /**
     * 注册人脸状态码，准备注册
     */
    private static final int REGISTER_STATUS_READY = 0;
    /**
     * 注册人脸状态码，注册中
     */
    private static final int REGISTER_STATUS_PROCESSING = 1;
    /**
     * 注册人脸状态码，注册结束（无论成功失败）
     */
    private static final int REGISTER_STATUS_DONE = 2;

    private int registerStatus = REGISTER_STATUS_DONE;
    /**
     * 用于记录人脸识别相关状态
     */
    private ConcurrentHashMap<Integer, Integer> requestFeatureStatusMap = new ConcurrentHashMap<>();
    /**
     * 用于记录人脸特征提取出错重试次数
     */
    private ConcurrentHashMap<Integer, Integer> extractErrorRetryMap = new ConcurrentHashMap<>();
    /**
     * 用于存储活体值
     */
    private ConcurrentHashMap<Integer, Integer> livenessMap = new ConcurrentHashMap<>();
    /**
     * 用于存储活体检测出错重试次数
     */
    private ConcurrentHashMap<Integer, Integer> livenessErrorRetryMap = new ConcurrentHashMap<>();

    /**
     * 当FR成功，活体未成功时，FR等待活体的时间
     */
    private static final int WAIT_LIVENESS_INTERVAL = 100;
    /**
     * 失败重试间隔时间（ms）
     */
    private static final long FAIL_RETRY_INTERVAL = 1000;
    /**
     * 出错重试最大次数
     */
    private static final int MAX_RETRY_TIME = 3;

    private CompositeDisposable getFeatureDelayedDisposables = new CompositeDisposable();
    private CompositeDisposable delayFaceTaskCompositeDisposable = new CompositeDisposable();

    public FaceLiveDetectPresenter(FaceCompareView baseView, IPageControl control) {
        this.pageControl = control;
        this.baseView = baseView;

        this.faceRectView = pageControl.getActivity().findViewById(R.id.single_camera_face_rect_view);
        this.cameraView = pageControl.getActivity().findViewById(R.id.textureView);

        rs = RenderScript.create(AppUtil.getApplication());
        yuvToRgbIntrinsic = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs));
    }

    /**
     * 激活并初始化人证引擎。
     *
     * @return
     * @throws
     */
    public boolean activeAndInit() throws Exception {
        return initEngine();
    }

    /**
     * 初始化人脸引擎方法
     *
     * @return
     * @throws
     */
    private boolean initEngine() throws Exception {
        if (!new File(FileConfig.getStoragePath() + File.separator + "active_result.dat").exists()) {
            ToastUtil.toastShort(pageControl.getContext(), "离线授权文件丢失，授权失败");
            isInit = false;
            return false;
        }
        int activeCode = FaceEngine.activeOffline(AppUtil.getApplication(),
                FileConfig.getStoragePath() + File.separator + "active_result.dat");
        if (activeCode != 0 && activeCode != 90114) {
            isInit = false;
            ComomAction.writeLogThread("zjiankf", "人脸引擎激活失败：" + activeCode);
            return isInit;
        } else {
            //初始化
            ftEngine = new FaceEngine();
            ftInitCode = ftEngine.init(pageControl.getContext(), DetectMode.ASF_DETECT_MODE_VIDEO, DetectFaceOrientPriority.ASF_OP_0_ONLY,
                    16, MAX_DETECT_NUM, FaceEngine.ASF_FACE_DETECT);

            frEngine = new FaceEngine();
            frInitCode = frEngine.init(pageControl.getContext(), DetectMode.ASF_DETECT_MODE_IMAGE, DetectFaceOrientPriority.ASF_OP_0_ONLY,
                    16, MAX_DETECT_NUM, FaceEngine.ASF_FACE_RECOGNITION | FaceEngine.ASF_FACE_DETECT);

            flEngine = new FaceEngine();
            flInitCode = flEngine.init(pageControl.getContext(), DetectMode.ASF_DETECT_MODE_IMAGE, DetectFaceOrientPriority.ASF_OP_0_ONLY,
                    16, MAX_DETECT_NUM, FaceEngine.ASF_LIVENESS);


            if (ftInitCode != ErrorInfo.MOK) {
                String error = pageControl.getActivity().getString(R.string.specific_engine_init_failed, "ftEngine", ftInitCode);
                ComomAction.writeLogThread("ftEngine", error);
            }
            if (frInitCode != ErrorInfo.MOK) {
                String error = pageControl.getActivity().getString(R.string.specific_engine_init_failed, "frEngine", frInitCode);
                ComomAction.writeLogThread("frEngine", error);
            }
            if (flInitCode != ErrorInfo.MOK) {
                String error = pageControl.getActivity().getString(R.string.specific_engine_init_failed, "flEngine", flInitCode);
                ComomAction.writeLogThread("frEngine", error);
            }

            isInit = (ftInitCode == ErrorInfo.MOK && frInitCode == ErrorInfo.MOK && flInitCode == ErrorInfo.MOK);
            return isInit;
        }

    }


    public void detachView() {

        if (ftInitCode == ErrorInfo.MOK && ftEngine != null) {
            synchronized (ftEngine) {
                int ftUnInitCode = ftEngine.unInit();
            }
        }
        if (frInitCode == ErrorInfo.MOK && frEngine != null) {
            synchronized (frEngine) {
                int frUnInitCode = frEngine.unInit();
            }
        }
        if (flInitCode == ErrorInfo.MOK && flEngine != null) {
            synchronized (flEngine) {
                int flUnInitCode = flEngine.unInit();
            }
        }

        if (getFeatureDelayedDisposables != null) {
            getFeatureDelayedDisposables.clear();
        }
        if (delayFaceTaskCompositeDisposable != null) {
            delayFaceTaskCompositeDisposable.clear();
        }
        if (faceHelper != null) {
            ConfigUtil.setTrackedFaceCount(pageControl.getActivity(), faceHelper.getTrackedFaceCount());
            faceHelper.release();
            faceHelper = null;
        }
        if (faceFeatures != null) {
            faceFeatures.clear();
            faceFeatures = null;
        }
    }


    @Override
    public void onCameraOpened(Camera camera, int cameraId, int displayOrientation, boolean isMirror) {
        Camera.Size lastPreviewSize = previewSize;
        previewSize = camera.getParameters().getPreviewSize();
        drawHelper = new DrawHelper(previewSize.width, previewSize.height, cameraView.getWidth(), cameraView.getHeight(), displayOrientation
                , cameraId, isMirror, false, false);
        // 切换相机的时候可能会导致预览尺寸发生变化
        if (faceHelper == null ||
                lastPreviewSize == null ||
                lastPreviewSize.width != previewSize.width || lastPreviewSize.height != previewSize.height) {
            Integer trackedFaceCount = null;
            // 记录切换时的人脸序号
            if (faceHelper != null) {
                trackedFaceCount = faceHelper.getTrackedFaceCount();
                faceHelper.release();
            }
            faceHelper = new FaceHelper.Builder()
                    .ftEngine(ftEngine)
                    .frEngine(frEngine)
                    .flEngine(flEngine)
                    .frQueueSize(MAX_DETECT_NUM)
                    .flQueueSize(MAX_DETECT_NUM)
                    .previewSize(previewSize)
                    .faceListener(this)
                    .trackedFaceCount(trackedFaceCount == null ? ConfigUtil.getTrackedFaceCount(pageControl.getActivity()) : trackedFaceCount)
                    .build();
        }
    }

    @Override
    public void onPreview(byte[] data, Camera camera) {
        if (baseView != null) {
            baseView.onPreview();
        }
        if (isInit) {
            //初始化成功之后,绘制人脸。
            List<FacePreviewInfo> facePreviewInfoList = faceHelper.onPreviewFrameNoMask(data,0);
            if (facePreviewInfoList != null && faceRectView != null && drawHelper != null) {
                drawPreviewInfo(facePreviewInfoList);
            }
            clearLeftFace(facePreviewInfoList);

            if (facePreviewInfoList != null && facePreviewInfoList.size() > 0) {
                Rect rect = facePreviewInfoList.get(0).getFaceInfo().getRect();
                if (rect.right - rect.left <= 200) {
                    return;
                } else if (rect.top < 60 || rect.bottom > 540) {
                    return;
                } else {
                    if (Math.abs(rect.left - (800 - rect.right)) > 160) {
                        return;
                    }
                }
                if (previewSize != null) {
                    for (int i = 0; i < facePreviewInfoList.size(); i++) {
                        Integer status = requestFeatureStatusMap.get(facePreviewInfoList.get(i).getTrackId());
                        /**
                         * 在活体检测开启，在人脸识别状态不为成功或人脸活体状态不为处理中（ANALYZING）且不为处理完成（ALIVE、NOT_ALIVE）时重新进行活体检测
                         */
                        if (livenessDetect && (status == null || status != RequestFeatureStatus.SUCCEED)) {
                            Integer liveness = livenessMap.get(facePreviewInfoList.get(i).getTrackId());
                            if (liveness == null
                                    || (liveness != LivenessInfo.ALIVE && liveness != LivenessInfo.NOT_ALIVE && liveness != RequestLivenessStatus.ANALYZING)) {
                                livenessMap.put(facePreviewInfoList.get(i).getTrackId(), RequestLivenessStatus.ANALYZING);

                                Log.e("zjiankf","id:"+facePreviewInfoList.get(i).getTrackId()+"---"+ RequestLivenessStatus.ANALYZING);
                                faceHelper.requestFaceLiveness(data, facePreviewInfoList.get(i).getFaceInfo(), previewSize.width, previewSize.height, FaceEngine.CP_PAF_NV21, facePreviewInfoList.get(i).getTrackId(), LivenessType.RGB);
                            }
                        }
                        /**
                         * 对于每个人脸，若状态为空或者为失败，则请求特征提取（可根据需要添加其他判断以限制特征提取次数），
                         * 特征提取回传的人脸特征结果在{@link FaceListener#onFaceFeatureInfoGet(FaceFeature, Integer, Integer)}中回传
                         */
                        if (status == null
                                || status == RequestFeatureStatus.TO_RETRY) {
                            requestFeatureStatusMap.put(facePreviewInfoList.get(i).getTrackId(), RequestFeatureStatus.SEARCHING);
                            faceHelper.requestFaceFeature(data, facePreviewInfoList.get(i).getFaceInfo(), previewSize.width, previewSize.height, FaceEngine.CP_PAF_NV21, facePreviewInfoList.get(i).getTrackId());
                        }
                    }
                }
            }


        }
    }


    private void drawPreviewInfo(List<FacePreviewInfo> facePreviewInfoList) {
        List<DrawInfo> drawInfoList = new ArrayList<>();
        for (int i = 0; i < facePreviewInfoList.size(); i++) {
            String name = faceHelper.getName(facePreviewInfoList.get(i).getTrackId());
            Integer liveness = livenessMap.get(facePreviewInfoList.get(i).getTrackId());
            Integer recognizeStatus = requestFeatureStatusMap.get(facePreviewInfoList.get(i).getTrackId());
            int color = RecognizeColor.COLOR_UNKNOWN;

            color = RecognizeColor.COLOR_UNKNOWN;
            if (recognizeStatus != null) {
                if (recognizeStatus == RequestFeatureStatus.FAILED) {
                    color = RecognizeColor.COLOR_FAILED;
                }
                if (recognizeStatus == RequestFeatureStatus.SUCCEED) {
                    color = RecognizeColor.COLOR_SUCCESS;
                }
            }
            Rect rect = facePreviewInfoList.get(i).getFaceInfo().getRect();
            if (rect.right - rect.left <= 200) {
                name = "请靠近摄像头";
            } else if (rect.top < 60 || rect.bottom > 540) {
                name = "请居于屏幕中央";
            } else {
                if (Math.abs(rect.left - (800 - rect.right)) > 160) {
                    name = "请居于屏幕中央";
                } else {
                    name = "";
                }
            }

            if (liveness != null && liveness != LivenessInfo.ALIVE) {
                color = RecognizeColor.COLOR_FAILED;
                name = "活体检测未通过";
            }


            drawInfoList.add(new DrawInfo(drawHelper.adjustRect(facePreviewInfoList.get(i).getFaceInfo().getRect()),
                    GenderInfo.UNKNOWN, AgeInfo.UNKNOWN_AGE, liveness == null ? LivenessInfo.UNKNOWN : liveness, color,
                    name));
        }
        drawHelper.draw(faceRectView, drawInfoList);
    }

    /**
     * 删除已经离开的人脸
     *
     * @param facePreviewInfoList 人脸和trackId列表
     */
    private void clearLeftFace(List<FacePreviewInfo> facePreviewInfoList) {
        if (facePreviewInfoList == null || facePreviewInfoList.size() == 0) {
            requestFeatureStatusMap.clear();
            livenessMap.clear();
            livenessErrorRetryMap.clear();
            extractErrorRetryMap.clear();
            if (getFeatureDelayedDisposables != null) {
                getFeatureDelayedDisposables.clear();
            }
            return;
        }
        Enumeration<Integer> keys = requestFeatureStatusMap.keys();
        while (keys.hasMoreElements()) {
            int key = keys.nextElement();
            boolean contained = false;
            for (FacePreviewInfo facePreviewInfo : facePreviewInfoList) {
                if (facePreviewInfo.getTrackId() == key) {
                    contained = true;
                    break;
                }
            }
            if (!contained) {
                requestFeatureStatusMap.remove(key);
                livenessMap.remove(key);
                livenessErrorRetryMap.remove(key);
                extractErrorRetryMap.remove(key);
            }
        }
    }

    /**
     * 将map中key对应的value增1回传
     *
     * @param countMap map
     * @param key      key
     * @return 增1后的value
     */
    public int increaseAndGetValue(Map<Integer, Integer> countMap, int key) {
        if (countMap == null) {
            return 0;
        }
        Integer value = countMap.get(key);
        if (value == null) {
            value = 0;
        }
        countMap.put(key, ++value);
        return value;
    }


    @Override
    public void onCameraClosed() {

    }

    @Override
    public void onCameraError(Exception e) {
        ComomAction.writeLogThread("相机打开异常", "相机打开异常" + e.getMessage());
    }

    @Override
    public void onCameraConfigurationChanged(int cameraID, int displayOrientation) {

    }

    @Override
    public void onFail(Exception e) {

    }

    @Override
    public void onFaceFeatureInfoGet(@Nullable FaceFeature faceFeature, Integer requestId, Integer errorCode, byte[] nv21) {
//FR成功
        if (faceFeature != null) {


        }
        //特征提取失败
        else {
            if (increaseAndGetValue(extractErrorRetryMap, requestId) > MAX_RETRY_TIME) {
                extractErrorRetryMap.put(requestId, 0);
                ComomAction.writeLogThread("人证比对结果", "人脸置信度低，特征提取失败");
                // 在尝试最大次数后，特征提取仍然失败，则认为识别未通过
                requestFeatureStatusMap.put(requestId, RequestFeatureStatus.FAILED);
                retryRecognizeDelayed(requestId);
            } else {
                requestFeatureStatusMap.put(requestId, RequestFeatureStatus.TO_RETRY);
            }
        }
    }

    /**
     * 延迟 FAIL_RETRY_INTERVAL 重新进行人脸识别
     *
     * @param requestId 人脸ID
     */
    private void retryRecognizeDelayed(final Integer requestId) {
        requestFeatureStatusMap.put(requestId, RequestFeatureStatus.FAILED);
        Observable.timer(FAIL_RETRY_INTERVAL, TimeUnit.MILLISECONDS)
                .subscribe(new Observer<Long>() {
                    Disposable disposable;

                    @Override
                    public void onSubscribe(Disposable d) {
                        disposable = d;
                        delayFaceTaskCompositeDisposable.add(disposable);
                    }

                    @Override
                    public void onNext(Long aLong) {

                    }

                    @Override
                    public void onError(Throwable e) {
                        e.printStackTrace();
                    }

                    @Override
                    public void onComplete() {
                        requestFeatureStatusMap.put(requestId, RequestFeatureStatus.TO_RETRY);
                        delayFaceTaskCompositeDisposable.remove(disposable);
                    }
                });
    }


    /**
     * 延迟 FAIL_RETRY_INTERVAL 重新进行活体检测
     *
     * @param requestId 人脸ID
     */
    private void retryLivenessDetectDelayed(final Integer requestId) {
        Observable.timer(FAIL_RETRY_INTERVAL, TimeUnit.MILLISECONDS)
                .subscribe(new Observer<Long>() {
                    Disposable disposable;

                    @Override
                    public void onSubscribe(Disposable d) {
                        disposable = d;
                        delayFaceTaskCompositeDisposable.add(disposable);
                    }

                    @Override
                    public void onNext(Long aLong) {

                    }

                    @Override
                    public void onError(Throwable e) {
                        e.printStackTrace();
                    }

                    @Override
                    public void onComplete() {

                        livenessMap.put(requestId, LivenessInfo.UNKNOWN);
                        delayFaceTaskCompositeDisposable.remove(disposable);
                    }
                });
    }

    @Override
    public void onFaceLivenessInfoGet(@Nullable LivenessInfo livenessInfo, byte[] nv21, FaceInfo faceInfo, Integer requestId, Integer errorCode) {
        if (livenessInfo != null) {
            Log.e("zjiankf","onFaceLivenessInfoGet:id"+requestId+"---"+livenessInfo.getLiveness());
            int liveness = livenessInfo.getLiveness();
            livenessMap.put(requestId, liveness);
            if (liveness == LivenessInfo.ALIVE){
                pageControl.getActivity().runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        JsonObject data = new JsonObject();
                        Bitmap bitmap =  nv21ToBitmap(nv21,800,600);

                        Rect rect = faceInfo.getRect();
                        bitmap = Bitmap.createBitmap(bitmap, rect.left - 10, rect.top-40, rect.right - rect.left + 10, rect.bottom - rect.top+40);
                        data.addProperty("facebase64", BitmapUtils.bitmapToBase64_NOWRAP(bitmap));

                        pageControl.getActivity().runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                baseView.refreshViewWithDetectSuccess(data);
                            }
                        });

                    }
                });
            }
            // 非活体，重试
            if (liveness == LivenessInfo.NOT_ALIVE) {
                retryLivenessDetectDelayed(requestId);
            }
        } else {
            if (increaseAndGetValue(livenessErrorRetryMap, requestId) > MAX_RETRY_TIME) {
                livenessErrorRetryMap.put(requestId, 0);
                retryLivenessDetectDelayed(requestId);
            } else {
                livenessMap.put(requestId, LivenessInfo.UNKNOWN);
            }
        }
    }
    private RenderScript rs;
    private ScriptIntrinsicYuvToRGB yuvToRgbIntrinsic;
    private Type.Builder yuvType, rgbaType;
    private Allocation in, out;
    public Bitmap nv21ToBitmap(byte[] nv21, int width, int height) {
        if (yuvType == null) {
            yuvType = new Type.Builder(rs, Element.U8(rs)).setX(nv21.length);
            in = Allocation.createTyped(rs, yuvType.create(), Allocation.USAGE_SCRIPT);

            rgbaType = new Type.Builder(rs, Element.RGBA_8888(rs)).setX(width).setY(height);
            out = Allocation.createTyped(rs, rgbaType.create(), Allocation.USAGE_SCRIPT);
        }

        in.copyFrom(nv21);

        yuvToRgbIntrinsic.setInput(in);
        yuvToRgbIntrinsic.forEach(out);

        Bitmap bmpout = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        out.copyTo(bmpout);

        return bmpout;

    }
}
